Wie tokenisiere ich eine Zeichenfolge in C++?
Frage
Java verfügt über eine praktische Split-Methode:
String str = "The quick brown fox";
String[] results = str.split(" ");
Gibt es eine einfache Möglichkeit, dies in C++ zu tun?
Lösung
Ihr einfacher Fall kann leicht aufgebaut wird mit den std::string::find
Verfahren . Jedoch einen Blick auf Boost.Tokenizer . Es ist toll. Boost-Regel hat einige sehr coole String-Tools.
Andere Tipps
Die Erhöhung tokenizer Klasse diese Art machen kann die Sache ganz einfach:
#include <iostream>
#include <string>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
using namespace std;
using namespace boost;
int main(int, char**)
{
string text = "token, test string";
char_separator<char> sep(", ");
tokenizer< char_separator<char> > tokens(text, sep);
BOOST_FOREACH (const string& t, tokens) {
cout << t << "." << endl;
}
}
Aktualisiert für C ++ 11:
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>
using namespace std;
using namespace boost;
int main(int, char**)
{
string text = "token, test string";
char_separator<char> sep(", ");
tokenizer<char_separator<char>> tokens(text, sep);
for (const auto& t : tokens) {
cout << t << "." << endl;
}
}
Hier ist ein echtes einfach:
#include <vector>
#include <string>
using namespace std;
vector<string> split(const char *str, char c = ' ')
{
vector<string> result;
do
{
const char *begin = str;
while(*str != c && *str)
str++;
result.push_back(string(begin, str));
} while (0 != *str++);
return result;
}
Mit strtok. Meiner Meinung nach gibt es keine Notwendigkeit, eine Klasse um Tokenisieren zu bauen, es sei denn strtok Sie nicht mit dem, was Sie brauchen. Es ist vielleicht nicht, aber in mehr als 15 Jahren von verschiedenem Parsing-Code in C und C ++ zu schreiben, habe ich immer verwendet strtok. Hier ist ein Beispiel
char myString[] = "The quick brown fox";
char *p = strtok(myString, " ");
while (p) {
printf ("Token: %s\n", p);
p = strtok(NULL, " ");
}
Ein paar Einschränkungen (was Ihre Bedürfnisse vielleicht nicht passen). Der String ist „zerstört“ in den Prozess, was bedeutet, dass EOS-Zeichen in den inline delimter Flecken angeordnet sind. Korrekte Verwendung muss möglicherweise eine nicht-const-Version des Strings machen. Sie können auch die Liste der Mitte Parse-Trennzeichen ändern.
In meiner eigenen Meinung nach ist der obige Code wesentlich einfacher und einfacher zu bedienen als eine separate Klasse für sie zu schreiben. Für mich ist dies eine der Funktionen, die die Sprache bietet und es tut es gut und sauber. Es ist einfach eine „C basierte“ Lösung. Es ist angemessen, es ist einfach, und Sie haben nicht viel zusätzlichen Code schreiben: -)
Eine weitere schnelle Möglichkeit ist getline
zu verwenden. So etwas wie:
stringstream ss("bla bla");
string s;
while (getline(ss, s, ' ')) {
cout << s << endl;
}
Wenn Sie möchten, können Sie ein einfaches split()
Verfahren machen eine vector<string>
Rückkehr, das ist
sehr nützlich.
Sie können Ströme verwenden, Iteratoren und den Kopieralgorithmus dies ziemlich direkt zu tun.
#include <string>
#include <vector>
#include <iostream>
#include <istream>
#include <ostream>
#include <iterator>
#include <sstream>
#include <algorithm>
int main()
{
std::string str = "The quick brown fox";
// construct a stream from the string
std::stringstream strstr(str);
// use stream iterators to copy the stream to the vector as whitespace separated strings
std::istream_iterator<std::string> it(strstr);
std::istream_iterator<std::string> end;
std::vector<std::string> results(it, end);
// send the vector to stdout.
std::ostream_iterator<std::string> oit(std::cout);
std::copy(results.begin(), results.end(), oit);
}
Keine Straftat Leute, aber für so ein einfaches Problem, Sie machen Dinge Weg zu kompliziert. Es gibt eine Menge Gründe, Erhöhung . Aber für etwas so einfach, es ist wie eine Fliege mit einem 20 # Schlitten schlagen.
void
split( vector<string> & theStringVector, /* Altered/returned value */
const string & theString,
const string & theDelimiter)
{
UASSERT( theDelimiter.size(), >, 0); // My own ASSERT macro.
size_t start = 0, end = 0;
while ( end != string::npos)
{
end = theString.find( theDelimiter, start);
// If at end, use length=maxLength. Else use length=end-start.
theStringVector.push_back( theString.substr( start,
(end == string::npos) ? string::npos : end - start));
// If at end, use start=maxSize. Else use start=end+delimiter.
start = ( ( end > (string::npos - theDelimiter.size()) )
? string::npos : end + theDelimiter.size());
}
}
Zum Beispiel (für Dougs Fall)
#define SHOW(I,X) cout << "[" << (I) << "]\t " # X " = \"" << (X) << "\"" << endl
int
main()
{
vector<string> v;
split( v, "A:PEP:909:Inventory Item", ":" );
for (unsigned int i = 0; i < v.size(); i++)
SHOW( i, v[i] );
}
Und ja, könnten wir split () haben einen neuen Vektor zurückzukehren, anstatt eine nebenbei. Es ist trivial zu wickeln und Überlastung. Aber je nachdem, was ich tue, finde ich es oft besser wiederverwenden bereits bestehende Objekte, statt immer neue zu schaffen. (Nur so lange, wie ich vergessen Sie nicht, den Vektor zu leeren dazwischen!)
Referenz: http://www.cplusplus.com/reference/string/string / .
(Ich schrieb ursprünglich eine Antwort auf Dougs Frage: C ++ Strings Ändern und Extrahieren basierend auf Separatoren (geschlossen) . Aber da Martin Yorker diese Frage mit einem Zeiger hier geschlossen ... ich werde einfach meinen Code verallgemeinern.)
Erhöhung hat eine starke Split-Funktion: boost :: Algorithmus :: Split .
Beispielprogramm:
#include <vector>
#include <boost/algorithm/string.hpp>
int main() {
auto s = "a,b, c ,,e,f,";
std::vector<std::string> fields;
boost::split(fields, s, boost::is_any_of(","));
for (const auto& field : fields)
std::cout << "\"" << field << "\"\n";
return 0;
}
Ausgabe:
"a"
"b"
" c "
""
"e"
"f"
""
Eine Lösung mit regex_token_iterator
s:
#include <iostream>
#include <regex>
#include <string>
using namespace std;
int main()
{
string str("The quick brown fox");
regex reg("\\s+");
sregex_token_iterator iter(str.begin(), str.end(), reg, -1);
sregex_token_iterator end;
vector<string> vec(iter, end);
for (auto a : vec)
{
cout << a << endl;
}
}
Ich weiß, dass Sie für eine C ++ Lösung gefragt, aber Sie könnten betrachten dies hilfreich:
Qt
#include <QString>
...
QString str = "The quick brown fox";
QStringList results = str.split(" ");
Der Vorteil gegenüber Erhöhung in diesem Beispiel ist, dass es zu einer Zuordnung zu Ihrem Beitrag Code ein direkter ist.
Mehr unter Qt Dokumentation
Hier ist ein Beispiel tokenizer Klasse, die möglicherweise tun, was Sie wollen
//Header file
class Tokenizer
{
public:
static const std::string DELIMITERS;
Tokenizer(const std::string& str);
Tokenizer(const std::string& str, const std::string& delimiters);
bool NextToken();
bool NextToken(const std::string& delimiters);
const std::string GetToken() const;
void Reset();
protected:
size_t m_offset;
const std::string m_string;
std::string m_token;
std::string m_delimiters;
};
//CPP file
const std::string Tokenizer::DELIMITERS(" \t\n\r");
Tokenizer::Tokenizer(const std::string& s) :
m_string(s),
m_offset(0),
m_delimiters(DELIMITERS) {}
Tokenizer::Tokenizer(const std::string& s, const std::string& delimiters) :
m_string(s),
m_offset(0),
m_delimiters(delimiters) {}
bool Tokenizer::NextToken()
{
return NextToken(m_delimiters);
}
bool Tokenizer::NextToken(const std::string& delimiters)
{
size_t i = m_string.find_first_not_of(delimiters, m_offset);
if (std::string::npos == i)
{
m_offset = m_string.length();
return false;
}
size_t j = m_string.find_first_of(delimiters, i);
if (std::string::npos == j)
{
m_token = m_string.substr(i);
m_offset = m_string.length();
return true;
}
m_token = m_string.substr(i, j - i);
m_offset = j;
return true;
}
Beispiel:
std::vector <std::string> v;
Tokenizer s("split this string", " ");
while (s.NextToken())
{
v.push_back(s.GetToken());
}
Dies ist eine einfache STL-only-Lösung (~ 5 Zeilen!) Mit std::find
und std::find_first_not_of
die Wiederholungen des Trennzeichens (wie Leerzeichen oder Punkte zum Beispiel) behandelt sowie vorderen und hinteren Begrenzungszeichen:
#include <string>
#include <vector>
void tokenize(std::string str, std::vector<string> &token_v){
size_t start = str.find_first_not_of(DELIMITER), end=start;
while (start != std::string::npos){
// Find next occurence of delimiter
end = str.find(DELIMITER, start);
// Push back the token found into vector
token_v.push_back(str.substr(start, end-start));
// Skip all occurences of the delimiter to find new start
start = str.find_first_not_of(DELIMITER, end);
}
}
Ausprobieren leben !
pystring ist eine kleine Bibliothek, die eine Reihe von Python-String-Funktionen implementiert, einschließlich der Split-Methode:
#include <string>
#include <vector>
#include "pystring.h"
std::vector<std::string> chunks;
pystring::split("this string", chunks);
// also can specify a separator
pystring::split("this-string", chunks, "-");
Ich gab diese Antwort für ähnliche Frage.
Das Rad nicht neu erfinden. Ich habe eine Reihe von Bibliotheken verwendet und die schnellste und flexibelste Ich bin gekommen, across ist: C ++ String Toolkit Bibliothek .
Hier ist ein Beispiel dafür, wie es zu benutzen, dass ich sonst wo auf der Stackoverflow gebucht haben.
#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>
const char *whitespace = " \t\r\n\f";
const char *whitespace_and_punctuation = " \t\r\n\f;,=";
int main()
{
{ // normal parsing of a string into a vector of strings
std::string s("Somewhere down the road");
std::vector<std::string> result;
if( strtk::parse( s, whitespace, result ) )
{
for(size_t i = 0; i < result.size(); ++i )
std::cout << result[i] << std::endl;
}
}
{ // parsing a string into a vector of floats with other separators
// besides spaces
std::string t("3.0, 3.14; 4.0");
std::vector<float> values;
if( strtk::parse( s, whitespace_and_punctuation, values ) )
{
for(size_t i = 0; i < values.size(); ++i )
std::cout << values[i] << std::endl;
}
}
{ // parsing a string into specific variables
std::string u("angle = 45; radius = 9.9");
std::string w1, w2;
float v1, v2;
if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
{
std::cout << "word " << w1 << ", value " << v1 << std::endl;
std::cout << "word " << w2 << ", value " << v2 << std::endl;
}
}
return 0;
}
Dieses Beispiel prüfen. Es könnte Ihnen helfen ..
#include <iostream>
#include <sstream>
using namespace std;
int main ()
{
string tmps;
istringstream is ("the dellimiter is the space");
while (is.good ()) {
is >> tmps;
cout << tmps << "\n";
}
return 0;
}
MFC / ATL hat einen sehr schönen tokenizer. Von MSDN:
CAtlString str( "%First Second#Third" );
CAtlString resToken;
int curPos= 0;
resToken= str.Tokenize("% #",curPos);
while (resToken != "")
{
printf("Resulting token: %s\n", resToken);
resToken= str.Tokenize("% #",curPos);
};
Output
Resulting Token: First
Resulting Token: Second
Resulting Token: Third
Sie können einfach eine Bibliothek für reguläre Ausdrücke und lösen, dass reguläre Ausdrücke verwenden.
Verwenden Ausdruck (\ w +) und die Variable in \ 1 (oder $ 1 in Abhängigkeit von der Bibliothek Implementierung von regulären Ausdrücken).
Wenn Sie bereit sind, C zu verwenden, können Sie die strtok Funktion. Sie sollten ihr Augenmerk auf Multi-Thread-Probleme zahlen, wenn es zu benutzen.
Für einfache Sachen, die ich benutzen Sie einfach die folgenden:
unsigned TokenizeString(const std::string& i_source,
const std::string& i_seperators,
bool i_discard_empty_tokens,
std::vector<std::string>& o_tokens)
{
unsigned prev_pos = 0;
unsigned pos = 0;
unsigned number_of_tokens = 0;
o_tokens.clear();
pos = i_source.find_first_of(i_seperators, pos);
while (pos != std::string::npos)
{
std::string token = i_source.substr(prev_pos, pos - prev_pos);
if (!i_discard_empty_tokens || token != "")
{
o_tokens.push_back(i_source.substr(prev_pos, pos - prev_pos));
number_of_tokens++;
}
pos++;
prev_pos = pos;
pos = i_source.find_first_of(i_seperators, pos);
}
if (prev_pos < i_source.length())
{
o_tokens.push_back(i_source.substr(prev_pos));
number_of_tokens++;
}
return number_of_tokens;
}
Cowardly Haftungsausschluss: Ich schreibe Echtzeit-Datenverarbeitungssoftware, wo die Daten in durch Binärdateien kommen, Sockets oder einen API-Aufruf (I / O-Karten, Kamera). Ich benutze nie diese Funktion für etwas komplizierter oder zeitkritisch als das Lesen externe Konfigurationsdateien beim Start.
Viele zu kompliziert Vorschläge hier. Versuchen Sie diese einfache std :: string Lösung:
using namespace std;
string someText = ...
string::size_type tokenOff = 0, sepOff = tokenOff;
while (sepOff != string::npos)
{
sepOff = someText.find(' ', sepOff);
string::size_type tokenLen = (sepOff == string::npos) ? sepOff : sepOff++ - tokenOff;
string token = someText.substr(tokenOff, tokenLen);
if (!token.empty())
/* do something with token */;
tokenOff = sepOff;
}
Ich dachte, das war es, was der >>
Operator auf String-Streams war:
string word; sin >> word;
Adam Pierces Antwort bietet eine handgesponnene tokenizer in einem const char*
nehmen. Es ist ein bisschen problematischer mit Iteratoren zu tun, weil ein string
Ende Iterator Inkrementieren ist nicht definiert. Das heißt, da string str{ "The quick brown fox" }
wir können dies mit Sicherheit erreichen:
auto start = find(cbegin(str), cend(str), ' ');
vector<string> tokens{ string(cbegin(str), start) };
while (start != cend(str)) {
const auto finish = find(++start, cend(str), ' ');
tokens.push_back(string(start, finish));
start = finish;
}
Wenn Sie sich auf abstrakte Komplexität der Suche unter Verwendung von Standard-Funktionalität, wie On Freund schlägt strtok
ist eine einfache Option:
vector<string> tokens;
for (auto i = strtok(data(str), " "); i != nullptr; i = strtok(nullptr, " ")) tokens.push_back(i);
Wenn Sie keinen Zugang zu C 17 ++ Sie benötigen data(str)
wie in diesem Beispiel ersetzen: http : //ideone.com/8kAGoa
Obwohl im Beispiel nicht gezeigt, strtok
braucht nicht das gleiche Trennzeichen für jedes Token zu verwenden. Zusammen mit dieser Vorteil allerdings gibt es mehrere Nachteile:
-
strtok
kann nicht auf mehrerenstrings
gleichzeitig verwendet werden: Entweder einnullptr
muss Zeichenüber die aktuellestring
oder eine neuechar*
weitergeben werden müssen tokenize weitergegeben werden (es gibt einige Nicht-Standard-Implementierungen, die dies jedoch unterstützen, wie zum Beispiel:strtok_s
) - Aus dem gleichen Grunde
strtok
kann nicht auf mehreren Threads gleichzeitig verwendet werden (dies jedoch Implementierung definiert sein kann, zum Beispiel: Visual Studio-Implementierung ist die thread~~POS=TRUNC ) - Beim
strtok
diestring
ändert es auf in Betrieb ist, so kann es nicht aufconst string
s,const char*
s verwendet werden, oder Zeichenketten, mitstrtok
einer dieser Punkte auf tokenize oder auf einemstring
zu betreiben, die Inhalte ist gewahrt werden müssen,str
hätte kopiert werden, dann könnte die Kopie auf betrieben werden
Sowohl die bisherigen Methoden können keine Zeichen übersetzten vector
an Ort und Stelle erzeugen, das heißt, ohne sie in eine Helferfunktion abstrahiert sie nicht const vector<string> tokens
initialisieren. Diese Funktionalität und die Fähigkeit zu akzeptieren jeder white-space Trennzeichen kann ein istream_iterator
. Zum Beispiel gegeben: const string str{ "The quick \tbrown \nfox" }
wir können dies tun:
istringstream is{ str };
const vector<string> tokens{ istream_iterator<string>(is), istream_iterator<string>() };
Der erforderliche Aufbau eines istringstream
für diese Option hat weit höhere Kosten als die bisherigen zwei Optionen, aber diese Kosten üblicherweise in Kosten der string
Zuordnung verborgen ist.
Falls keine der oben genannten Optionen flexable genug für Ihre tokenization Bedürfnisse sind, die flexibelste Option wird mit einem regex_token_iterator
natürlich mit dieser Flexibilität kommt höhere Kosten, aber auch dies ist in der string
Verrechnungskostenart wahrscheinlich versteckt. Sagen Sie zum Beispiel, das wir auf der Grundlage nicht entgangen Kommas tokenize wollen, auch white-space essen, angesichts der folgenden Eingabe: const string str{ "The ,qu\\,ick ,\tbrown, fox" }
wir dies tun können:
const regex re{ "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" };
const vector<string> tokens{ sregex_token_iterator(cbegin(str), cend(str), re, 1), sregex_token_iterator() };
Hier ist ein Ansatz, den Sie (wie strsep) Kontrolle darüber, ob leere Token enthalten kann oder (wie strtok) ausgeschlossen.
#include <string.h> // for strchr and strlen
/*
* want_empty_tokens==true : include empty tokens, like strsep()
* want_empty_tokens==false : exclude empty tokens, like strtok()
*/
std::vector<std::string> tokenize(const char* src,
char delim,
bool want_empty_tokens)
{
std::vector<std::string> tokens;
if (src and *src != '\0') // defensive
while( true ) {
const char* d = strchr(src, delim);
size_t len = (d)? d-src : strlen(src);
if (len or want_empty_tokens)
tokens.push_back( std::string(src, len) ); // capture token
if (d) src += len+1; else break;
}
return tokens;
}
Es scheint mir seltsam, dass hier mit allen uns Geschwindigkeit bewusst Nerds auf, damit niemand eine Version vorgelegt hat, die eine Kompilierung verwendet generierte Tabelle für das Trennzeichen (zB Implementierung weiter unten) nachschlagen. Mit Hilfe einer Nachschlagetabelle und Iteratoren sollte std :: regex in Effizienz schlagen, wenn Sie benötigen es nicht regex zu schlagen, nur verwenden, dessen Standard wie von C ++ 11 und super flexibel.
Einige regex vorgeschlagen hat bereits, aber für den noobs hier ist ein verpacktes Beispiel, die genau das tun soll, was der OP erwartet:
std::vector<std::string> split(std::string::const_iterator it, std::string::const_iterator end, std::regex e = std::regex{"\\w+"}){
std::smatch m{};
std::vector<std::string> ret{};
while (std::regex_search (it,end,m,e)) {
ret.emplace_back(m.str());
std::advance(it, m.position() + m.length()); //next start position = match position + match length
}
return ret;
}
std::vector<std::string> split(const std::string &s, std::regex e = std::regex{"\\w+"}){ //comfort version calls flexible version
return split(s.cbegin(), s.cend(), std::move(e));
}
int main ()
{
std::string str {"Some people, excluding those present, have been compile time constants - since puberty."};
auto v = split(str);
for(const auto&s:v){
std::cout << s << std::endl;
}
std::cout << "crazy version:" << std::endl;
v = split(str, std::regex{"[^e]+"}); //using e as delim shows flexibility
for(const auto&s:v){
std::cout << s << std::endl;
}
return 0;
}
Wenn wir müssen schneller sein und die Einschränkung zu akzeptieren, dass alle Zeichen 8 Bits sein müssen, können wir eine Verweistabelle zum Zeitpunkt der Kompilierung mit metaprogramming machen:
template<bool...> struct BoolSequence{}; //just here to hold bools
template<char...> struct CharSequence{}; //just here to hold chars
template<typename T, char C> struct Contains; //generic
template<char First, char... Cs, char Match> //not first specialization
struct Contains<CharSequence<First, Cs...>,Match> :
Contains<CharSequence<Cs...>, Match>{}; //strip first and increase index
template<char First, char... Cs> //is first specialization
struct Contains<CharSequence<First, Cs...>,First>: std::true_type {};
template<char Match> //not found specialization
struct Contains<CharSequence<>,Match>: std::false_type{};
template<int I, typename T, typename U>
struct MakeSequence; //generic
template<int I, bool... Bs, typename U>
struct MakeSequence<I,BoolSequence<Bs...>, U>: //not last
MakeSequence<I-1, BoolSequence<Contains<U,I-1>::value,Bs...>, U>{};
template<bool... Bs, typename U>
struct MakeSequence<0,BoolSequence<Bs...>,U>{ //last
using Type = BoolSequence<Bs...>;
};
template<typename T> struct BoolASCIITable;
template<bool... Bs> struct BoolASCIITable<BoolSequence<Bs...>>{
/* could be made constexpr but not yet supported by MSVC */
static bool isDelim(const char c){
static const bool table[256] = {Bs...};
return table[static_cast<int>(c)];
}
};
using Delims = CharSequence<'.',',',' ',':','\n'>; //list your custom delimiters here
using Table = BoolASCIITable<typename MakeSequence<256,BoolSequence<>,Delims>::Type>;
Mit der im Ort eine getNextToken
Funktion zu machen ist einfach:
template<typename T_It>
std::pair<T_It,T_It> getNextToken(T_It begin,T_It end){
begin = std::find_if(begin,end,std::not1(Table{})); //find first non delim or end
auto second = std::find_if(begin,end,Table{}); //find first delim or end
return std::make_pair(begin,second);
}
Mit ihm ist auch einfach:
int main() {
std::string s{"Some people, excluding those present, have been compile time constants - since puberty."};
auto it = std::begin(s);
auto end = std::end(s);
while(it != std::end(s)){
auto token = getNextToken(it,end);
std::cout << std::string(token.first,token.second) << std::endl;
it = token.second;
}
return 0;
}
Hier ist ein anschauliches Beispiel: http://ideone.com/GKtkLQ
Ich weiß, diese Frage ist schon beantwortet, aber ich möchte dazu beitragen. Vielleicht ist meine Lösung ein einfach wenig, aber das ist, was ich kam mit:
vector<string> get_words(string const& text)
{
vector<string> result;
string tmp = text;
size_t first_pos = 0;
size_t second_pos = tmp.find(" ");;
while (second_pos != string::npos)
{
if (first_pos != second_pos)
{
string word = tmp.substr(first_pos, second_pos - first_pos);
result.push_back(word);
}
tmp = tmp.substr(second_pos + 1);
second_pos = tmp.find(" ");
}
result.push_back(tmp);
return result;
}
Bitte kommentieren, wenn es ein besserer Ansatz, um etwas in meinem Code ist oder wenn etwas falsch ist.
Es gibt keinen direkten Weg, dies zu tun. Siehe dieses Code-Projekt Quellcode um herauszufinden, wie eine Klasse für diese bauen .
Sie können die Vorteile der boost :: nehmen make_find_iterator. Etwas ähnliches wie folgt aus:
template<typename CH>
inline vector< basic_string<CH> > tokenize(
const basic_string<CH> &Input,
const basic_string<CH> &Delimiter,
bool remove_empty_token
) {
typedef typename basic_string<CH>::const_iterator string_iterator_t;
typedef boost::find_iterator< string_iterator_t > string_find_iterator_t;
vector< basic_string<CH> > Result;
string_iterator_t it = Input.begin();
string_iterator_t it_end = Input.end();
for(string_find_iterator_t i = boost::make_find_iterator(Input, boost::first_finder(Delimiter, boost::is_equal()));
i != string_find_iterator_t();
++i) {
if(remove_empty_token){
if(it != i->begin())
Result.push_back(basic_string<CH>(it,i->begin()));
}
else
Result.push_back(basic_string<CH>(it,i->begin()));
it = i->end();
}
if(it != it_end)
Result.push_back(basic_string<CH>(it,it_end));
return Result;
}
Wenn die maximale Länge der Eingabezeichenfolge ist bekannt seinen Token versehen, kann man dies ausnutzen und eine sehr schnelle Version implementieren. Ich bin die Grundidee unten skizzieren, die sowohl von strtok inspiriert wurde () und die „Suffixarray“ -Daten Struktur beschrieben Jon Bentley „Programming Perls“ 2. Auflage, Kapitel 15. Die C ++ Klasse in diesem Fall gibt nur einige Organisation und Komfort von Nutzen. Die Implementierung gezeigt kann leicht zur Entfernung verlängert werden führende und nachgestellte Leerzeichen in den Token.
Grundsätzlich kann man die Trennzeichen mit strang abschließende ‚\ 0'-Zeichen ersetzen und stellen Zeiger auf die Token withing der modifizierten Zeichenfolge. Im Extremfall, wenn die Zeichenfolge nur von Separatoren bestehen, erhält man fade Länge plus 1 resultierende leere Token. Es ist praktisch, die Zeichenfolge zu duplizieren zu geändert werden.
Header-Datei:
class TextLineSplitter
{
public:
TextLineSplitter( const size_t max_line_len );
~TextLineSplitter();
void SplitLine( const char *line,
const char sep_char = ',',
);
inline size_t NumTokens( void ) const
{
return mNumTokens;
}
const char * GetToken( const size_t token_idx ) const
{
assert( token_idx < mNumTokens );
return mTokens[ token_idx ];
}
private:
const size_t mStorageSize;
char *mBuff;
char **mTokens;
size_t mNumTokens;
inline void ResetContent( void )
{
memset( mBuff, 0, mStorageSize );
// mark all items as empty:
memset( mTokens, 0, mStorageSize * sizeof( char* ) );
// reset counter for found items:
mNumTokens = 0L;
}
};
Implementattion-Datei:
TextLineSplitter::TextLineSplitter( const size_t max_line_len ):
mStorageSize ( max_line_len + 1L )
{
// allocate memory
mBuff = new char [ mStorageSize ];
mTokens = new char* [ mStorageSize ];
ResetContent();
}
TextLineSplitter::~TextLineSplitter()
{
delete [] mBuff;
delete [] mTokens;
}
void TextLineSplitter::SplitLine( const char *line,
const char sep_char /* = ',' */,
)
{
assert( sep_char != '\0' );
ResetContent();
strncpy( mBuff, line, mMaxLineLen );
size_t idx = 0L; // running index for characters
do
{
assert( idx < mStorageSize );
const char chr = line[ idx ]; // retrieve current character
if( mTokens[ mNumTokens ] == NULL )
{
mTokens[ mNumTokens ] = &mBuff[ idx ];
} // if
if( chr == sep_char || chr == '\0' )
{ // item or line finished
// overwrite separator with a 0-terminating character:
mBuff[ idx ] = '\0';
// count-up items:
mNumTokens ++;
} // if
} while( line[ idx++ ] );
}
Ein Szenario der Nutzung sei:
// create an instance capable of splitting strings up to 1000 chars long:
TextLineSplitter spl( 1000 );
spl.SplitLine( "Item1,,Item2,Item3" );
for( size_t i = 0; i < spl.NumTokens(); i++ )
{
printf( "%s\n", spl.GetToken( i ) );
}
Ausgabe:
Item1
Item2
Item3
boost::tokenizer
ist dein Freund, aber in Erwägung ziehen Ihr Code portabel mit Bezug auf die Internationalisierung (i18n) Fragen von wstring
/ wchar_t
statt der Legacy-string
/ char
Typen verwendet wird.
#include <iostream>
#include <boost/tokenizer.hpp>
#include <string>
using namespace std;
using namespace boost;
typedef tokenizer<char_separator<wchar_t>,
wstring::const_iterator, wstring> Tok;
int main()
{
wstring s;
while (getline(wcin, s)) {
char_separator<wchar_t> sep(L" "); // list of separator characters
Tok tok(s, sep);
for (Tok::iterator beg = tok.begin(); beg != tok.end(); ++beg) {
wcout << *beg << L"\t"; // output (or store in vector)
}
wcout << L"\n";
}
return 0;
}
Einfach C ++ Code (Standard C ++ 98), mehrere Trennzeichen (angegeben in einem std :: string) akzeptiert, verwendet nur Vektoren, Streicher und Iteratoren.
#include <iostream>
#include <vector>
#include <string>
#include <stdexcept>
std::vector<std::string>
split(const std::string& str, const std::string& delim){
std::vector<std::string> result;
if (str.empty())
throw std::runtime_error("Can not tokenize an empty string!");
std::string::const_iterator begin, str_it;
begin = str_it = str.begin();
do {
while (delim.find(*str_it) == std::string::npos && str_it != str.end())
str_it++; // find the position of the first delimiter in str
std::string token = std::string(begin, str_it); // grab the token
if (!token.empty()) // empty token only when str starts with a delimiter
result.push_back(token); // push the token into a vector<string>
while (delim.find(*str_it) != std::string::npos && str_it != str.end())
str_it++; // ignore the additional consecutive delimiters
begin = str_it; // process the remaining tokens
} while (str_it != str.end());
return result;
}
int main() {
std::string test_string = ".this is.a.../.simple;;test;;;END";
std::string delim = "; ./"; // string containing the delimiters
std::vector<std::string> tokens = split(test_string, delim);
for (std::vector<std::string>::const_iterator it = tokens.begin();
it != tokens.end(); it++)
std::cout << *it << std::endl;
}